﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlServerCe;
using BlueDot.Mobile.Core;
using System.IO;

namespace SqlMergeReplication.Services
{
    public delegate void ReplicationEventHandler(object sender, ReplicationEventArgs e);

    public class ReplicationService : IDisposable, IReplicationService
    {
        public event EventHandler SynchronizationCompleted;
        public event EventHandler<DataEventArgs<string>> StatusChanged;

        #region Fields

        private ReplicationConfiguration configuration;
        private DatabaseAction databaseAction;
        private DateTime endTime;
        private string failureDetails;
        private string failureMessage;
        private List<string> publicationsToSync;
        private static SqlCeReplication sqlReplication;
        private DateTime startTime;

        #endregion

        #region Properties

        #endregion

        #region public

        public void Abort()
        {
            if (sqlReplication != null)
            {
                sqlReplication.CancelSynchronize();
                UpdateView("Aborted by user", ReplicationEventArgs.SyncStatus.AbortedByUser);
            }
        }

        public void Execute()
        {
            startTime = DateTime.Now;
            UpdateView("Starting replication", ReplicationEventArgs.SyncStatus.Initializing);

            ConfigureReplication();

            StartReplication();
        }

        private bool isInitialized = false;

        public bool Initialized
        {
            get
            {
                return isInitialized;
            }
        }

        public void Initialize(ReplicationConfiguration configuration)
        {
            try
            {
                this.configuration = configuration;

                publicationsToSync = new List<string>(this.configuration.Publications.Length);
                publicationsToSync.AddRange(this.configuration.Publications);
                databaseAction = DatabaseAction.NoAction;
                failureMessage = "";
                failureDetails = "";

                if (!File.Exists(this.configuration.LocalDatabaseName))
                {
                    databaseAction = DatabaseAction.CreateNew;
                }
                else if (this.configuration.CreateSubscription)
                {
                    databaseAction = DatabaseAction.CreateSubscription;
                }
                isInitialized = true;
            }
            catch (Exception ex)
            {
                UpdateView(ex.ToString(), ReplicationEventArgs.SyncStatus.Failed);
            }
        }

        #endregion

        #region private

        private void OnSyncComplete(IAsyncResult ar)
        {
            endTime = DateTime.Now;
            SqlCeReplication repl = (SqlCeReplication)ar.AsyncState;

            try
            {
                repl.EndSynchronize(ar);
                repl.SaveProperties();

                publicationsToSync.RemoveAt(0);
                if (publicationsToSync.Count > 0)
                {
                    StartReplication();
                }
                else
                {
                    TimeSpan elapsed = endTime - startTime;

                    UpdateView("Finished", ReplicationEventArgs.SyncStatus.SyncComplete);
                    UpdateView(repl.PublisherChanges.ToString() + " Local changes", ReplicationEventArgs.SyncStatus.SyncComplete);
                    UpdateView(repl.SubscriberChanges.ToString() + " Server changes", ReplicationEventArgs.SyncStatus.SyncComplete);
                    UpdateView("Transmit time: " +
                               elapsed.Hours + "h " +
                               elapsed.Minutes + "m " +
                               elapsed.Seconds + "s " +
                               elapsed.Milliseconds + "ms", ReplicationEventArgs.SyncStatus.SyncComplete);

                    // Send the User Define Finished Success Message
                    UpdateView(configuration.FinishedSuccessMessage, ReplicationEventArgs.SyncStatus.SyncComplete);
                    OnSynchronizationCompleted();
                }
            }
            catch (SqlCeException ex)
            {
                // This is the error for when the HOST_NAME changes
                if (ex.NativeError == 28587)
                {
                    // TODO: Throw an error to the user here
                    UpdateView("HOST_NAME has changed: Reinit", ReplicationEventArgs.SyncStatus.HostChangedError);
                    databaseAction = DatabaseAction.ReinitSubscription;
                    StartReplication();
                }
                else
                {
                    failureMessage = ex.Message;
                    failureDetails = ex.ToString();
                    UpdateView(failureDetails, ReplicationEventArgs.SyncStatus.Failed);

                    // Send the User Defined Finished Error Message
                    UpdateView(configuration.FinishedErrorMessage, ReplicationEventArgs.SyncStatus.Failed);
                    OnSynchronizationCompleted();
                }
            }
        }

        private void OnTableStartDownload(IAsyncResult ar, string tableName)
        {
            UpdateView("Downloading: " + tableName, ReplicationEventArgs.SyncStatus.BeginDownload);
        }

        private void OnTableStartUpload(IAsyncResult ar, string tableName)
        {
            UpdateView("Uploading: " + tableName, ReplicationEventArgs.SyncStatus.BeginUpload);
        }

        private void OnTableSync(IAsyncResult ar, int percentComplete)
        {
            UpdateView("", ReplicationEventArgs.SyncStatus.PercentComplete, percentComplete);
        }

        private void ConfigureReplication()
        {
            if (sqlReplication == null)
            {
                sqlReplication = new SqlCeReplication();
                sqlReplication.InternetUrl = configuration.InternetUrl;
                sqlReplication.InternetLogin = configuration.InternetLogin;
                sqlReplication.InternetPassword = configuration.InternetPassword;
                sqlReplication.Publisher = configuration.Publisher;
                sqlReplication.PublisherDatabase = configuration.PublisherDatabase;
                if (configuration.AuthenticationType.Equals("ntauthentication", StringComparison.InvariantCultureIgnoreCase))
                {
                    sqlReplication.PublisherSecurityMode = SecurityType.NTAuthentication;
                }
                else
                {
                    sqlReplication.PublisherSecurityMode = SecurityType.DBAuthentication;
                }
                sqlReplication.Subscriber = configuration.DeviceName;
                sqlReplication.SubscriberConnectionString =
                    string.Format("Data Source={0}", configuration.LocalDatabaseName);

                sqlReplication.HostName = configuration.HostName;
            }
        }

        private void OnSynchronizationCompleted()
        {
            if (SynchronizationCompleted != null)
            {
                SynchronizationCompleted(this, EventArgs.Empty);
            }
        }

        private void StartReplication()
        {
            if (publicationsToSync.Count > 0)
            {
                // Add the publication -
                sqlReplication.Publication = publicationsToSync[0];

                switch (databaseAction)
                {
                    case DatabaseAction.CreateNew:
                        sqlReplication.AddSubscription(AddOption.CreateDatabase);
                        databaseAction = DatabaseAction.CreateSubscription;
                        break;
                    case DatabaseAction.CreateSubscription:
                        try
                        {
                            sqlReplication.AddSubscription(AddOption.ExistingDatabase);
                        }
                        catch (SqlCeException)
                        {
                        }
                        break;
                    case DatabaseAction.ReinitSubscription:
                        sqlReplication.ReinitializeSubscription(false);
                        break;
                    case DatabaseAction.NoAction:
                        break;
                }

                UpdateView("Initializing subscription: " + sqlReplication.Publication,
                           ReplicationEventArgs.SyncStatus.InitializingSubscription);

                IAsyncResult ar = sqlReplication.BeginSynchronize(
                    new AsyncCallback(OnSyncComplete),
                    new OnStartTableUpload(OnTableStartUpload),
                    new OnStartTableDownload(OnTableStartDownload),
                    new OnSynchronization(OnTableSync),
                    sqlReplication);
            }
        }

        private void UpdateView(string message, ReplicationEventArgs.SyncStatus status)
        {
            UpdateView(message, status, -1);
        }

        private void UpdateView(string message, ReplicationEventArgs.SyncStatus status, int percentComplete)
        {
            if (StatusChanged != null)
            {
                if (percentComplete >= 0)
                {
                    StatusChanged(null, new DataEventArgs<string>(
                        string.Format("{0}: {1} - {2}%", status, message, percentComplete)));
                }
                else
                {
                    StatusChanged(null, new DataEventArgs<string>(
                        string.Format("{0}: {1}", status, message, percentComplete)));
                }
            }
        }

        #endregion

        private bool disposed = false;

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    // Dispose managed resources.
                    //if (sqlReplication != null)
                    //{
                    //    sqlReplication.Dispose();
                    //}                    
                }
                disposed = true;
            }
        }

        ~ReplicationService()
        {
            Dispose(false);
        }

        #region Nested type: DatabaseAction

        internal enum DatabaseAction
        {
            CreateNew,
            CreateSubscription,
            ReinitSubscription,
            NoAction
        }
        #endregion
    }

    public class ReplicationEventArgs : EventArgs
    {
        #region Fields

        private int percentComplete;

        private SyncStatus replicationStatus;
        private string status;

        #endregion

        #region Constructors

        public ReplicationEventArgs()
        {
            status = "";
            replicationStatus = SyncStatus.Undefined;
        }

        #endregion

        #region Properties

        public int PercentComplete
        {
            get { return percentComplete; }
            set { percentComplete = value; }
        }

        internal SyncStatus ReplicationStatus
        {
            get { return replicationStatus; }
            set { replicationStatus = value; }
        }

        public string Status
        {
            get { return status; }
            set { status = value; }
        }

        #endregion

        #region Nested type: SyncStatus

        internal enum SyncStatus
        {
            Initializing,
            InitializingSubscription,
            PercentComplete,
            BeginUpload,
            BeginDownload,
            SyncComplete,
            HostChangedError,
            AbortedByUser,
            Failed,
            Undefined
        }
        #endregion
    }

    public class ReplicationConfiguration
    {
        #region Fields

        private string authenticationTypeField;
        private bool createSubscriptionField;
        private string deviceNameField;
        private string finishedErrorMessageField;
        private string finishedSuccessMessageField;
        private string hostNameField;

        private string internetLoginField;

        private string internetPasswordField;
        private string internetUrlField;
        private string localDatabaseNameField;

        private string localDatabasePasswordField;
        private string[] publicationsField;
        private string publisherDatabaseField;
        private string publisherField;

        #endregion

        #region Properties

        public string AuthenticationType
        {
            get { return authenticationTypeField; }
            set { authenticationTypeField = value; }
        }

        public bool CreateSubscription
        {
            get { return createSubscriptionField; }
            set { createSubscriptionField = value; }
        }

        public string DeviceName
        {
            get { return deviceNameField; }
            set { deviceNameField = value; }
        }

        public string FinishedErrorMessage
        {
            get { return finishedErrorMessageField; }
            set { finishedErrorMessageField = value; }
        }

        public string FinishedSuccessMessage
        {
            get { return finishedSuccessMessageField; }
            set { finishedSuccessMessageField = value; }
        }


        public string HostName
        {
            get { return hostNameField; }
            set { hostNameField = value; }
        }

        public string InternetLogin
        {
            get { return internetLoginField; }
            set { internetLoginField = value; }
        }

        public string InternetPassword
        {
            get { return internetPasswordField; }
            set { internetPasswordField = value; }
        }

        public string InternetUrl
        {
            get { return internetUrlField; }
            set { internetUrlField = value; }
        }

        public string LocalDatabaseName
        {
            get { return localDatabaseNameField; }
            set { localDatabaseNameField = value; }
        }

        public string LocalDatabasePassword
        {
            get { return localDatabasePasswordField; }
            set { localDatabasePasswordField = value; }
        }

        public string[] Publications
        {
            get { return publicationsField; }
            set { publicationsField = value; }
        }

        public string Publisher
        {
            get { return publisherField; }
            set { publisherField = value; }
        }

        public string PublisherDatabase
        {
            get { return publisherDatabaseField; }
            set { publisherDatabaseField = value; }
        }

        #endregion properties
    }

}
